#include <initguid.h>
#include <stdio.h>
#include "MyColorConverter.h"


HRESULT QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_INVALIDARG;

    const AM_MEDIA_TYPE& mt = *pmt;

    if (mt.majortype != MEDIATYPE_Video)
        return S_FALSE;

    if (mt.subtype == MEDIASUBTYPE_YV12)
        __noop;

    else if (mt.subtype == MEDIASUBTYPE_YUY2)
        __noop;

    else if (mt.subtype == MEDIASUBTYPE_YUYV)
        __noop;

    else
        return S_FALSE;

    if (mt.pbFormat == 0)
        return S_FALSE;

    const BITMAPINFOHEADER* pbmih;

    if (mt.formattype == FORMAT_VideoInfo)
    {
        if (mt.cbFormat < sizeof(VIDEOINFOHEADER))
            return S_FALSE;

        const VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat);

        if (vih.AvgTimePerFrame <= 0)
            return S_FALSE;

        pbmih = &vih.bmiHeader;
    }
    else if (mt.formattype == FORMAT_VideoInfo2)
    {
            return S_FALSE;

    }
    else
        return S_FALSE;

    const BITMAPINFOHEADER& bmih = *pbmih;

    if (bmih.biSize != sizeof(BITMAPINFOHEADER))  //TODO: liberalize
        return S_FALSE;

    if (bmih.biWidth <= 0)
        return S_FALSE;

    if (bmih.biWidth % 2)  //TODO
        return S_FALSE;

    if (bmih.biHeight == 0)
        return S_FALSE;

    if (bmih.biHeight % 2)  //TODO
        return S_FALSE;

    if (bmih.biCompression != mt.subtype.Data1)
        return S_FALSE;

    return S_OK;
}
HRESULT ReceiveConnection(
    IPin* pin,
    const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_INVALIDARG;

    HRESULT hr ;


    hr = QueryAccept(pmt);

    if (hr != S_OK)
        return VFW_E_TYPE_NOT_ACCEPTED;

    const AM_MEDIA_TYPE& mt = *pmt;


    if (FAILED(hr))
        return hr;



    return S_OK;
}
//--------------------

MyColorConverter::MyColorConverter()
	:CTransformFilter(NAME("MyColorConverter"), NULL, CLSID_MyColorConverter)
{ 
   /* Initialize any private variables here. */
	printf("MyColorConverter()\n");
	this->pInputType = NULL;
}

MyColorConverter::MyColorConverter(TCHAR *tszName,LPUNKNOWN punk,HRESULT *phr) :
    CTransformFilter(tszName, punk, CLSID_MyColorConverter)
{ 
   /* Initialize any private variables here. */
}


MyColorConverter::~MyColorConverter(void)
{
}

HRESULT MyColorConverter::CheckInputType(const CMediaType *mtIn){
	printf("CheckInputType()\n");
	if(mtIn->majortype != MEDIATYPE_Video){
		return VFW_E_TYPE_NOT_ACCEPTED;
	}
	//yuv
	if(mtIn->subtype == MEDIASUBTYPE_YV12||mtIn->subtype==MEDIASUBTYPE_YUY2||mtIn->subtype==MEDIASUBTYPE_YUYV){
		if ((mtIn->formattype != FORMAT_VideoInfo) || (mtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
		{
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
		return S_OK;
	}
	//rgb
	if(mtIn->subtype == MEDIASUBTYPE_RGB24 || mtIn->subtype == MEDIASUBTYPE_RGB32){
		if ((mtIn->formattype != FORMAT_VideoInfo) || (mtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
		{
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
		VIDEOINFOHEADER *pVih = 
			reinterpret_cast<VIDEOINFOHEADER*>(mtIn->pbFormat);
		if ((pVih->bmiHeader.biBitCount != 24 && pVih->bmiHeader.biBitCount != 32) ||
			(pVih->bmiHeader.biCompression != BI_RGB))
		{
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		// Everything is good.
		return S_OK;
	}
	return VFW_E_TYPE_NOT_ACCEPTED;
}

HRESULT MyColorConverter::GetMediaType(int iPosition, CMediaType *pMediaType/*out*/){
	printf("GetMediaType()\n");
    if (m_pInput->IsConnected() == FALSE){
        return E_UNEXPECTED;
    }
    if (iPosition < 0){
        return E_INVALIDARG;
    }
	CheckPointer(pMediaType,E_POINTER);

    if (iPosition == 0){
		CMediaType mtIn;
        HRESULT hr = m_pInput->ConnectionMediaType(&mtIn);
        ASSERT(mtIn.formattype == FORMAT_VideoInfo);
        VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>(mtIn.pbFormat);
		LONG m_width = pVih->bmiHeader.biWidth;
		LONG m_height = pVih->bmiHeader.biHeight;

		//yuv
		if(mtIn.subtype == MEDIASUBTYPE_YV12||mtIn.subtype==MEDIASUBTYPE_YUY2||mtIn.subtype==MEDIASUBTYPE_YUYV){
			m_pInput->ConnectionMediaType(pMediaType);
			return S_OK;
		}
		//rgb
		if(mtIn.subtype == MEDIASUBTYPE_RGB24 || mtIn.subtype == MEDIASUBTYPE_RGB32){
			VIDEOINFOHEADER   vih; 
			memset( &vih, 0, sizeof(vih) );
			vih.bmiHeader.biCompression   =   MAKEFOURCC('Y','V','1','2');
			vih.bmiHeader.biBitCount      =   16; 
			vih.bmiHeader.biSize          =   sizeof(BITMAPINFOHEADER); 
			vih.bmiHeader.biWidth         =   m_width;
			vih.bmiHeader.biHeight        =   m_height;
			vih.bmiHeader.biPlanes        =   1; 
			vih.bmiHeader.biSizeImage     =   GetBitmapSize(&vih.bmiHeader); 
			vih.bmiHeader.biClrImportant  =   0; 

			vih.AvgTimePerFrame=10*1000*3600*24;// 24 hours. if 1 hour, throw 'Integer division by zero',odd.

			SetRectEmpty(&(vih.rcSource));
			SetRectEmpty(&(vih.rcTarget));

			pMediaType->SetType(&MEDIATYPE_Video); 
			pMediaType->SetFormatType(&FORMAT_VideoInfo); 
			pMediaType->SetFormat((BYTE*)&vih,sizeof(vih)); 
			pMediaType->SetSubtype(&MEDIASUBTYPE_YV12); 
			pMediaType->SetSampleSize(vih.bmiHeader.biSizeImage);

			return S_OK;
		}
	}
    // else
    return VFW_S_NO_MORE_ITEMS;
}

HRESULT MyColorConverter::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut){
	printf("CheckTransform()\n");
	DisplayType(TEXT("CheckTransform#mtIn"),mtIn);
	DisplayType(TEXT("CheckTransform#mtOut"),mtOut);
    // Check the major type.
    if (mtOut->majortype != MEDIATYPE_Video){
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
	//must be yuv
	if(mtOut->subtype != MEDIASUBTYPE_YV12 && mtOut->subtype!=MEDIASUBTYPE_YUY2 
		&& mtOut->subtype!=MEDIASUBTYPE_YUYV)
	{
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

    if ((mtOut->formattype != FORMAT_VideoInfo) || (mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Compare the bitmap information against the input type.
    ASSERT(mtIn->formattype == FORMAT_VideoInfo);
    BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
    BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);
    if ((pBmiOut->biWidth != pBmiIn->biWidth) || (pBmiOut->biHeight != pBmiIn->biHeight))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Compare source and target rectangles.
    RECT rcImg;
    SetRect(&rcImg, 0, 0, pBmiIn->biWidth, pBmiIn->biHeight);
    RECT *prcSrc = &((VIDEOINFOHEADER*)(mtIn->pbFormat))->rcSource;
    RECT *prcTarget = &((VIDEOINFOHEADER*)(mtOut->pbFormat))->rcTarget;
    if (!IsRectEmpty(prcSrc) && !EqualRect(prcSrc, &rcImg))
    {
        return VFW_E_INVALIDMEDIATYPE;
    }
    if (!IsRectEmpty(prcTarget) && !EqualRect(prcTarget, &rcImg))
    {
        return VFW_E_INVALIDMEDIATYPE;
    }
    // Everything is good.
    return S_OK;
}

HRESULT MyColorConverter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp){
	printf("DecideBufferSize()\n");
    AM_MEDIA_TYPE mt;
    HRESULT hr = m_pOutput->ConnectionMediaType(&mt);
    if (FAILED(hr)){
        return hr;
    }

    ASSERT(mt.formattype == FORMAT_VideoInfo);
    BITMAPINFOHEADER *pbmi = HEADER(mt.pbFormat);
    pProp->cbBuffer = DIBSIZE(*pbmi) * 2; 
    if (pProp->cbAlign == 0){
        pProp->cbAlign = 1;
    }
    if (pProp->cBuffers == 0){
        pProp->cBuffers = 1;
    }
    // Release the format block.
    FreeMediaType(mt);

    // Set allocator properties.
    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProp, &Actual);
    if (FAILED(hr)) {
        return hr;
    }
    // Even when it succeeds, check the actual result.
    if (pProp->cbBuffer > Actual.cbBuffer) {
        return E_FAIL;
    }
    return S_OK;
}

HRESULT MyColorConverter::Transform(IMediaSample *pIn, IMediaSample *pOut){
    BYTE *pBufferIn, *pBufferOut;
    HRESULT hr = pIn->GetPointer(&pBufferIn);
    if (FAILED(hr)){
        return hr;
    }
    hr = pOut->GetPointer(&pBufferOut);
    if (FAILED(hr)){
        return hr;
    }

	AM_MEDIA_TYPE* pInType = this->getInputType(pIn);

	DisplayType(TEXT("pInType"),pInType);

	if(pInType->subtype == MEDIASUBTYPE_YV12||pInType->subtype==MEDIASUBTYPE_YUY2||pInType->subtype==MEDIASUBTYPE_YUYV){
		this->Copy(pIn, pOut);
		return S_OK;
	}

    // Process the data.
    DWORD cbDest = 0;
	if(pInType->subtype == MEDIASUBTYPE_RGB24){
		cbDest = EncodeFrame(pBufferIn, 24, pBufferOut);
	}else if(pInType->subtype == MEDIASUBTYPE_RGB32){
		cbDest = EncodeFrame(pBufferIn, 32, pBufferOut);
	}
    KASSERT((long)cbDest <= pOut->GetSize());

    pOut->SetActualDataLength(cbDest);
    pOut->SetSyncPoint(TRUE);
    return S_OK;
}

#include "rgb2yuv.cpp"
DWORD MyColorConverter::EncodeFrame(BYTE *pBufferIn, int bitCount, BYTE *pBufferOut){
	RGBtoYUV420PSameSize((const unsigned char *)pBufferIn,pBufferOut,bitCount/8,1,320,240);
	int len = 320 * 240+(320 * 240)/2;
	return len;
}

AM_MEDIA_TYPE* MyColorConverter::getInputType(IMediaSample *pIn){
	HRESULT hr;
	AM_MEDIA_TYPE* pSType=NULL;
	hr = pIn->GetMediaType(&pSType);
	if(pSType!=NULL){
		this->pInputType = pSType;
	}
	if(this->pInputType==NULL){
		hr = m_pInput->ConnectionMediaType(&this->inputType);
		if(SUCCEEDED(hr)){
			this->pInputType = &inputType;
		}
	}

	return this->pInputType;
}


HRESULT MyColorConverter::Copy(IMediaSample *pSource, IMediaSample *pDest) const
{
    CheckPointer(pSource,E_POINTER);
    CheckPointer(pDest,E_POINTER);

    // Copy the sample data
    BYTE *pSourceBuffer, *pDestBuffer;
    long lSourceSize = pSource->GetActualDataLength();

    pSource->GetPointer(&pSourceBuffer);
    pDest->GetPointer(&pDestBuffer);

    CopyMemory((PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize);

    // Copy the sample times

    REFERENCE_TIME TimeStart, TimeEnd;
    if(NOERROR == pSource->GetTime(&TimeStart, &TimeEnd))
    {
        pDest->SetTime(&TimeStart, &TimeEnd);
    }

    LONGLONG MediaStart, MediaEnd;
    if(pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR)
    {
        pDest->SetMediaTime(&MediaStart,&MediaEnd);
    }

    // Copy the Sync point property

    HRESULT hr = pSource->IsSyncPoint();
    if(hr == S_OK)
    {
        pDest->SetSyncPoint(TRUE);
    }
    else if(hr == S_FALSE)
    {
        pDest->SetSyncPoint(FALSE);
    }
    else
    {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the media type

    AM_MEDIA_TYPE *pMediaType;
    pSource->GetMediaType(&pMediaType);
    pDest->SetMediaType(pMediaType);
    DeleteMediaType(pMediaType);

    // Copy the preroll property

    hr = pSource->IsPreroll();
    if(hr == S_OK)
    {
        pDest->SetPreroll(TRUE);
    }
    else if(hr == S_FALSE)
    {
        pDest->SetPreroll(FALSE);
    }
    else
    {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the discontinuity property

    hr = pSource->IsDiscontinuity();

    if(hr == S_OK)
    {
        pDest->SetDiscontinuity(TRUE);
    }
    else if(hr == S_FALSE)
    {
        pDest->SetDiscontinuity(FALSE);
    }
    else
    {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the actual data length

    long lDataLength = pSource->GetActualDataLength();
    pDest->SetActualDataLength(lDataLength);

    return NOERROR;

}